/*
 * Copyright © 2001-2013 Stéphane Raimbault <stephane.raimbault@gmail.com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifndef _MSC_VER
#include <unistd.h>
#endif

#include "../../include/public/port/tcp.h"

#include <sys/socket.h>
#include <netinet/tcp.h>
// 参数解释
// fd:网络连接描述符
// start:首次心跳侦测包发送之间的空闲时间
// interval:两次心跳侦测包之间的间隔时间
// count:探测次数，即将几次探测失败判定为TCP断开
static int set_tcp_keepAlive(int fd, int start, int interval, int count)
{
    int keepAlive = 1;
    if (fd < 0 || start < 0 || interval < 0 || count < 0)
        return -1;
    // 入口参数检查 ，编程的好习惯。//启用心跳机制，如果您想关闭，将keepAlive置零即可
    if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive)) == -1)
    {
        perror("setsockopt");
        return -1;
    }
    // 启用心跳机制开始到首次心跳侦测包发送之间的空闲时间
    if (setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, (void *)&start, sizeof(start)) == -1)
    {
        perror("setsockopt");
        return -1;
    } // 两次心跳侦测包之间的间隔时间
    if (setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, (void *)&interval, sizeof(interval)) == -1)
    {
        perror("setsockopt");
        return -1;
    } // 探测次数，即将几次探测失败判定为TCP断开
    if (setsockopt(fd, SOL_TCP, TCP_KEEPCNT, (void *)&count, sizeof(count)) == -1)
    {
        perror("setsockopt");
        return -1;
    }
    return 0;
}

#define _RESPONSE_TIMEOUT 500000
#define _BYTE_TIMEOUT 500000

static size_t strlcpy1(char *dest, const char *src, size_t dest_size)
{
    register char *d = dest;
    register const char *s = src;
    register size_t n = dest_size;

    /* Copy as many bytes as will fit */
    if (n != 0 && --n != 0)
    {
        do
        {
            if ((*d++ = *s++) == 0)
                break;
        } while (--n != 0);
    }

    /* Not enough room in dest, add NUL and traverse rest of src */
    if (n == 0)
    {
        if (dest_size != 0)
            *d = '\0'; /* NUL-terminate dest */
        while (*s++)
            ;
    }

    return (s - src - 1); /* count does not include NUL */
}

ssize_t tcp_send(tcp_t *ctx, const uint8_t *req, int req_length)
{
    /* MSG_NOSIGNAL
       Requests not to send SIGPIPE on errors on stream oriented
       sockets when the other end breaks the connection.  The EPIPE
       error is still returned. */
    return send(ctx->s, (const char *)req, req_length, MSG_NOSIGNAL);
}


ssize_t tcp_recv(tcp_t *ctx, uint8_t *rsp, int rsp_length, int timeout)
{
    int len = 0;
    int rc = 0;
    int err = 0;
    if (ctx->s == -1)
        return -1;

    while (rsp_length > 0)
    {
        rc = tcp_select(ctx, timeout);
        if (rc < 0)
        {
            err = rc;
            // printf("select error!\n");
            break;
        }
        else if (rc == 0)
        {
            // printf("select timeout!\n");
            break;
        }

        rc = recv(ctx->s, rsp + len, rsp_length, 0);
        if (rc <= 0)
        {
            if (rc < 0)
            {
                printf("---网络中断---!\n"); // 拔卡 拔网线
                err = rc;
            }
            else
            {
                // 服务器断开连接
                err = -1;
                printf("---服务器断开连接---!\n"); //
            }
            break;
        }
        len += rc;
        rsp_length -= rc;

        if (timeout < 0)
            timeout = 10;
        else
            timeout = timeout > _BYTE_TIMEOUT ? _BYTE_TIMEOUT : timeout;
    }

    if (err < 0)
        return err;
    else
        return len;
}

static int _tcp_set_ipv4_options(int s)
{
    int rc;
    int option;

    /* Set the TCP no delay flag */
    /* SOL_TCP = IPPROTO_TCP */
    option = 1;
    rc = setsockopt(s, IPPROTO_TCP, TCP_NODELAY,
                    (const void *)&option, sizeof(int));
    if (rc == -1)
    {
        return -1;
    }

    option = IPTOS_LOWDELAY;
    rc = setsockopt(s, IPPROTO_IP, IP_TOS,
                    (const void *)&option, sizeof(int));
    if (rc == -1)
    {
        return -1;
    }

    return 0;
}

static int _connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen,
                    const struct timeval *ro_tv)
{
    int rc = connect(sockfd, addr, addrlen);

    if (rc == -1 && errno == EINPROGRESS)
    {
        fd_set wset;
        int optval;
        socklen_t optlen = sizeof(optval);
        struct timeval tv = *ro_tv;

        /* Wait to be available in writing */
        FD_ZERO(&wset);
        FD_SET(sockfd, &wset);
        rc = select(sockfd + 1, NULL, &wset, NULL, &tv);
        if (rc <= 0)
        {
            /* Timeout or fail */
            return -1;
        }

        /* The connection is established if SO_ERROR and optval are set to 0 */
        rc = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&optval, &optlen);
        if (rc == 0 && optval == 0)
        {
            return 0;
        }
        else
        {
            errno = ECONNREFUSED;
            return -1;
        }
    }
    return rc;
}

/* Establishes a modbus TCP connection with a Modbus server. */
int tcp_connect(tcp_t *ctx)
{
    int rc;
    /* Specialized version of sockaddr for Internet socket address (same size) */
    struct sockaddr_in addr;
    int flags = SOCK_STREAM;

#ifdef SOCK_CLOEXEC
    flags |= SOCK_CLOEXEC;
#endif

#ifdef SOCK_NONBLOCK
    flags |= SOCK_NONBLOCK;
#endif

    ctx->s = socket(PF_INET, flags, 0);
    if (ctx->s == -1)
    {
        return -1;
    }

    rc = _tcp_set_ipv4_options(ctx->s);
    if (rc == -1)
    {
        close(ctx->s);
        ctx->s = -1;
        return -1;
    }

    set_tcp_keepAlive(ctx->s, 10, 5, 2);

    addr.sin_family = AF_INET;
    addr.sin_port = htons(ctx->port);
    addr.sin_addr.s_addr = inet_addr(ctx->ip);
    rc = _connect(ctx->s, (struct sockaddr *)&addr, sizeof(addr), &ctx->response_timeout);
    if (rc == -1)
    {
        close(ctx->s);
        ctx->s = -1;
        return -1;
    }

    return 0;
}

/* Closes the network connection and socket in TCP mode */
void tcp_close(tcp_t *ctx)
{
    if (ctx->s != -1)
    {
        shutdown(ctx->s, SHUT_RDWR);
        close(ctx->s);
        ctx->s = -1;
    }
}

int tcp_flush(tcp_t *ctx)
{
    int rc;
    int rc_sum = 0;

    do
    {
        /* Extract the garbage from the socket */
        char devnull[MAX_TCP_LEN];
        rc = recv(ctx->s, devnull, MAX_TCP_LEN, MSG_DONTWAIT);

        if (rc > 0)
        {
            rc_sum += rc;
        }
    } while (rc == MAX_TCP_LEN);

    return rc_sum;
}

/* Listens for any request from one or many modbus masters in TCP */
int tcp_listen(tcp_t *ctx, int nb_connection)
{
    int new_s;
    int enable;
    int flags;
    struct sockaddr_in addr;

    if (ctx == NULL)
    {
        errno = EINVAL;
        return -1;
    }

    flags = SOCK_STREAM;

#ifdef SOCK_CLOEXEC
    flags |= SOCK_CLOEXEC;
#endif

    new_s = socket(PF_INET, flags, IPPROTO_TCP);
    if (new_s == -1)
    {
        return -1;
    }

    enable = 1;
    if (setsockopt(new_s, SOL_SOCKET, SO_REUSEADDR,
                   (char *)&enable, sizeof(enable)) == -1)
    {
        close(new_s);
        return -1;
    }

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    /* If the modbus port is < to 1024, we need the setuid root. */
    addr.sin_port = htons(ctx->port);
    if (ctx->ip[0] == '0')
    {
        /* Listen any addresses */
        addr.sin_addr.s_addr = htonl(INADDR_ANY);
    }
    else
    {
        /* Listen only specified IP address */
        addr.sin_addr.s_addr = inet_addr(ctx->ip);
    }
    if (bind(new_s, (struct sockaddr *)&addr, sizeof(addr)) == -1)
    {
        close(new_s);
        return -1;
    }

    if (listen(new_s, nb_connection) == -1)
    {
        close(new_s);
        return -1;
    }

    return new_s;
}

int tcp_accept(tcp_t *ctx, int *s)
{
    struct sockaddr_in addr;
    socklen_t addrlen;

    if (ctx == NULL)
    {
        errno = EINVAL;
        return -1;
    }

    addrlen = sizeof(addr);
#ifdef HAVE_ACCEPT4
    /* Inherit socket flags and use accept4 call */
    ctx->s = accept4(*s, (struct sockaddr *)&addr, &addrlen, SOCK_CLOEXEC);
#else
    ctx->s = accept(*s, (struct sockaddr *)&addr, &addrlen);
#endif

    if (ctx->s == -1)
    {
        return -1;
    }

    if (ctx->debug)
    {
        printf("The client connection from %s is accepted\n",
               inet_ntoa(addr.sin_addr));
    }

    return ctx->s;
}
int tcp_select(tcp_t *ctx, int timeout)
{
    if (ctx->s == -1)
        return -1;

    fd_set rfds;
    FD_ZERO(&rfds);
    FD_SET(ctx->s, &rfds);
    struct timeval tv;
    tv.tv_sec = timeout / 1000;
    tv.tv_usec = (timeout % 1000) * 1000;

    struct timeval *time;
    if (timeout == -1)
        time = NULL;
    else
        time = &tv;

    int s_rc;
    while ((s_rc = select(ctx->s + 1, &rfds, NULL, NULL, time)) == -1)
    {
        if (errno == EINTR)
        {
            if (ctx->debug)
            {
                fprintf(stderr, "A non blocked signal was caught\n");
            }
            /* Necessary after an error */
            FD_ZERO(&rfds);
            FD_SET(ctx->s, &rfds);
        }
        else
        {
            return -1;
        }
    }

    if (s_rc == 0)
    {
        errno = ETIMEDOUT;
        // printf("timeout!\n");
        // return -1;
    }
    return s_rc;
}

void tcp_free(tcp_t *ctx)
{
    free(ctx);
}

tcp_t *new_tcp(const char *ip, int port)
{

    tcp_t *ctx;
    size_t dest_size;
    size_t ret_size;

    ctx = (tcp_t *)malloc(sizeof(tcp_t));
    if (ctx == NULL)
    {
        return NULL;
    }
    ctx->s = -1;
    // ctx->error_recovery = MODBUS_ERROR_RECOVERY_NONE;

    ctx->response_timeout.tv_sec = 0;
    ctx->response_timeout.tv_usec = TCP_RESPONSE_TIMEOUT;

    // ctx->byte_timeout.tv_sec = 0;
    // ctx->byte_timeout.tv_usec = _BYTE_TIMEOUT;

    // ctx->indication_timeout.tv_sec = 0;
    // ctx->indication_timeout.tv_usec = 0;

    if (ip != NULL)
    {
        dest_size = sizeof(char) * 16;
        ret_size = strlcpy1(ctx->ip, ip, dest_size);
        if (ret_size == 0)
        {
            fprintf(stderr, "The IP string is empty\n");
            free(ctx);
            errno = EINVAL;
            return NULL;
        }

        if (ret_size >= dest_size)
        {
            fprintf(stderr, "The IP string has been truncated\n");
            free(ctx);
            errno = EINVAL;
            return NULL;
        }
    }
    else
    {
        ctx->ip[0] = '0';
    }
    ctx->port = port;

    return ctx;
}
